import json
from datetime import datetime, timedelta
from enum import IntEnum

class TaskStatus(IntEnum):
    PENDING = 0
    RUNNING = 1
    COMPLETED = 2
    FAILED = 3
    FAILED_MAX_RETRIES = 4

task_to_exec_sql = '''
    SELECT id, task_id, func_name, status, exec_times, max_exec_times, next_exec_time, last_fetched_at, link, created_at, updated_at
        FROM tasks
    WHERE 
        status IN(%s, %s) 
    AND exec_times <= max_exec_times
    AND next_exec_time <= %s
    ORDER BY next_exec_time ASC
    LIMIT %s
'''


class Task:

    def __str__(self):
        return self.task_id

    def __init__(self, task_id=None, func_name=None, status=None, exec_times=None, max_exec_times=None, next_exec_time=None, last_fetched_at=None, link=None, created_at=None, updated_at=None, id=None):
        self.id = id
        self.task_id = task_id
        self.func_name = func_name
        self.status = status
        self.exec_times = exec_times
        self.max_exec_times = max_exec_times
        self.next_exec_time = next_exec_time
        self.last_fetched_at = last_fetched_at
        self.link = link
        self.created_at = created_at
        self.updated_at = updated_at

    @classmethod
    def add_task(cls, conn, task_id, func_name, max_exec_times, next_exec_time, link):
        cursor = conn.cursor()
        cursor.execute('''
            INSERT INTO tasks (task_id, func_name, max_exec_times, next_exec_time, link)
            SELECT %s, %s, %s, %s, %s
            WHERE NOT EXISTS (
                SELECT 1 FROM tasks WHERE task_id = %s
            )
        ''', (task_id, func_name, max_exec_times, next_exec_time, link, task_id))
        return cls(task_id=task_id, func_name=func_name, max_exec_times=max_exec_times, next_exec_time=next_exec_time, link=link)

    @classmethod
    def get_tasks_to_execute(cls, conn, entry_cnt):
        cursor = conn.cursor()
        current_time = datetime.now()
        cursor.execute(task_to_exec_sql, (TaskStatus.PENDING, TaskStatus.FAILED, current_time, entry_cnt))
        results = cursor.fetchall()
        tasks = []
        for row in results:
            tasks.append(cls(*row[1:], id=row[0]))
        return tasks

    @classmethod
    def update_task_status(cls, conn, task, success):
        cursor = conn.cursor()
        new_exec_times = task.exec_times + 1
        if success:
            new_status = TaskStatus.COMPLETED
            new_next_exec_time = None
        else:
            new_status = TaskStatus.FAILED
            if new_exec_times >= task.max_exec_times:
                new_status = TaskStatus.FAILED_MAX_RETRIES
                new_next_exec_time = None
            else:
                new_next_exec_time = datetime.now() + timedelta(minutes=1)
        cursor.execute('''
            UPDATE tasks
            SET exec_times = %s, status = %s, next_exec_time = %s
            WHERE task_id = %s
        ''', (new_exec_times, new_status, new_next_exec_time, task.task_id))

    @staticmethod
    def update_last_fetched_at(conn, task):
        cursor = conn.cursor()
        sql = "UPDATE tasks SET last_fetched_at = %s WHERE task_id = %s"
        cursor.execute(sql, (task.last_fetched_at, task.task_id))

    @classmethod
    def restart_task(cls, conn, task):
        cursor = conn.cursor()
        cursor.execute('''
            UPDATE tasks
            SET status = %s, exec_times = %s, next_exec_time = %s
            WHERE task_id = %s
        ''', (TaskStatus.PENDING, 0, datetime.now(), task.task_id))

    @classmethod
    def get_task(cls, conn, task_id):
        cursor = conn.cursor()
        cursor.execute('''
            SELECT id, task_id, func_name, status, exec_times, max_exec_times, next_exec_time, last_fetched_at, link, created_at, updated_at
            FROM tasks
            WHERE task_id = %s
        ''', (task_id,))
        result = cursor.fetchone()
        if result:
            return cls(*result[1:], id=result[0])
        return None